/*
 * BncsClient.cpp
 *
 *  Created on: Nov 18, 2008
 *      Author: nathan
 */

#define QT_NO_DEBUG_OUTPUT

#include "BncsClient.h"
#include "BncsMessage.h"
#include <bncsutil/bncsutil.h>
#include <QtDebug>

BncsClient::BncsClient(QObject* parent)
	: QObject(parent)
{
	sock = new QTcpSocket(this);
	QObject::connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(sockError(QAbstractSocket::SocketError)));
	QObject::connect(sock, SIGNAL(connected()), this, SLOT(sockConnect()));
	QObject::connect(sock, SIGNAL(hostFound()), this, SLOT(sockResolve()));
	QObject::connect(sock, SIGNAL(readyRead()), this, SLOT(sockReadReady()));
	QObject::connect(sock, SIGNAL(disconnected()), this, SLOT(sockDisconnect()));

	ds = QDataStream(sock);

	curMsgId = 0;
	curMsgLen = 0;
}

BncsClient::~BncsClient()
{
}

void BncsClient::sendMessage(BncsMessage msg)
{
	BncsHeader hdr;
	hdr.startByte = 0xff;
	hdr.messageId = msg.id();
	hdr.packetLen = hdr.size() + msg.size();

	hdr.writeTo(ds);
	msg.writeTo(ds);
}

/*
QString BncsClient::getHost()
{
	return this->host;
}

quint16 BncsClient::getPort()
{
	return this->port;
}

QString BncsClient::getUser()
{
	return this->user;
}

QString BncsClient::getPass()
{
	return this->pass;
}

QString BncsClient::getChannel()
{
	return this->channel;
}

QString BncsClient::getLobbyName()
{
	return this->lobbyName;
}

void* BncsClient::getContext()
{
	return this->context;
}

void BncsClient::setContext(void* context)
{
	this->context = context;
}
*/

void BncsClient::connect(ServiceAddress sa)
{
  qDebug() << "Attempting connection to " << sa.getHost()  << ":" << sa.getPort() << "...";
  sock->connectToHost(sa.getHost(), sa.getPort());
}

/*
void BncsClient::sendChat(QString message)
{
	QDataStream ds(sock);
	BncsChatCommand cc;

	cc.text = message.toAscii();

	sendBncsMessage(ds, cc);
}

void BncsClient::sendEmote(QString message)
{
	sendChat(tr("/me %1").arg(message));
}

void BncsClient::sendWhisper(QString message, QString target)
{
	sendChat(tr("/w %1 %2").arg(target).arg(message));
}

void BncsClient::joinChannel(QString channel)
{
	QDataStream ds(sock);
	BncsJoinChannel jc;

	jc.flags = 0x02;
	jc.name = channel.toAscii();

	sendBncsMessage(ds, jc);
}

void BncsClient::startLobby(QString name)
{
}

void BncsClient::endLobby()
{
}

void BncsClient::startLogout()
{
}
*/

void BncsClient::sockError(QAbstractSocket::SocketError err)
{
	qCritical() << "Socket error: " << sock->errorString();

	emit error(sock->errorString());
}

void BncsClient::sockResolve()
{
	qDebug() << "Resolved " << this->host << ".";
}

void BncsClient::sockConnect()
{
	qDebug() << "Connected to " << this->host << ":" << this->port << ".";

	/*
	QDataStream ds(sock);

	// Send protocol byte.
	ds << (quint8)0x01;

	// Send SID_AUTH_INFO (0x50)
	BncsAuthInfoClient baic;
	baic.protocolId = 0x00000000; // 00 00 00 00
	baic.platformId = 0x49583836; // 36 38 58 49
	baic.productId = 0x57335850; // 50 58 33 57
	baic.version = 0x00000016; // 16 00 00 00
	baic.language = 0x656e5553; // 53 55 6e 65
	baic.localIP = 0x00000000; // c0 a8 01 c3
	baic.tzbias = 0x00000000; // f0 00 00 00
	baic.localeId = 0x00000409; // 09 04 00 00
	baic.langId = 0x00000409; // 09 04 00 00
	baic.countryAbbr = "USA"; // 55 53 41 00
	baic.countryFull = "United States"; // 55 6e 69 74 65 64 20 53 74 61 74 65 73 00
	sendBncsMessage(ds, baic);
	*/
}

void BncsClient::sockReadReady()
{
	while (true)
	{
		if (!curMsgId)
		{
			BncsHeader hdr;

			// Read in a header and set curMsg* variables.
			if (sock->bytesAvailable() < hdr.size())
			{
				//qDebug() << "Not enough data available to read header.";
				return;
			}

			hdr.readFrom(ds);

			if (hdr.startByte != 0xff)
			{
				qCritical() << "Received header with invalid start byte (" << hdr.startByte << ")!";
				return;
			}

			curMsgId = hdr.messageId;
			curMsgLen = hdr.packetLen - hdr.size();

			//qDebug() << "Received header for message ID " << (int)curMsgId << " (length " << curMsgLen << ").";
		}

		if (sock->bytesAvailable() < curMsgLen)
		{
			//qDebug() << "Not enough data available to read payload.";
			return;
		}

		switch (curMsgId)
		{
			case 0x0a:
				processBncsEnterChat(ds);
				break;

			case 0x0f:
				processBncsChatEvent(ds);
				break;

			case 0x25:
				processBncsPing(ds);
				break;

			case 0x50:
				processBncsAuthInfo(ds);
				break;

			case 0x51:
				processBncsCheck(ds);
				break;

			case 0x53:
				processBncsAccountLogon(ds);
				break;

			case 0x54:
				processBncsAccountLogonProof(ds);
				break;

			default:
				qDebug() << "Ignoring unhandled message with ID " << (int)curMsgId << ".";
				ds.skipRawData(curMsgLen);
				break;
		}

		curMsgId = 0;
	}
}

void BncsClient::processBncsPing(QDataStream& ds)
{
	BncsPing ping;

	qDebug() << "Processing SID_PING...";

	ping.readFrom(ds);

	sendBncsMessage(ds, ping);
}

void BncsClient::processBncsAuthInfo(QDataStream& ds)
{
	BncsAuthInfoServer bais;
	BncsCheckClient bacc;

	qDebug() << "Processing SID_AUTH_INFO_SRV...";

	bais.readFrom(ds);

	bacc.clientToken = 0xBADBEEF;
	bacc.exeVersion = 0x011600B8;
	bacc.exeHash = 0x909998DB;
	bacc.numKeys = 2;
	bacc.bSpawn = 0;
	memset(bacc.keyInfo, 0, 2*sizeof(*bacc.keyInfo));
	bacc.exeInfo = "war3.exe 09/08/108 20:58:03 1568211";
	bacc.keyOwner = "Nathan D. Mickulicz";

	sendBncsMessage(ds, bacc);
}

void BncsClient::processBncsCheck(QDataStream& ds)
{
	BncsCheckServer bacs;
	BncsAccountLogonClient balc;

	qDebug() << "Processing SID_AUTH_CHECK_SRV...";
	bacs.readFrom(ds);

	if (bacs.result)
	{
		sock->abort();
		emit loginAborted(this, tr("failure at BNCS auth check (code %1)").arg(bacs.result));
		return;
	}

	memset(balc.clientKey, 0, 32);
	balc.user = user.toAscii();

	sendBncsMessage(ds, balc);
}

void BncsClient::processBncsAccountLogon(QDataStream& ds)
{
	BncsAccountLogonServer bals;
	BncsAccountLogonProofClient blpc;

	qDebug() << "Processing SID_AUTH_ACCOUNTLOGON_SRV...";

	bals.readFrom(ds);

	if (bals.status)
	{
		sock->abort();
		emit loginAborted(this, tr("failure at BNCS account logon (code %1)").arg(bals.status));
		return;
	}

	hashPassword(pass.toAscii().data(), (char*)blpc.clientProof);

	sendBncsMessage(ds, blpc);
}

void BncsClient::processBncsAccountLogonProof(QDataStream& ds)
{
	BncsAccountLogonProofServer blps;
	BncsNetGamePort bngp;
	BncsEnterChatClient ec;

	qDebug() << "Processing SID_AUTH_ACCOUNTLOGONPROOF_SRV...";

	blps.readFrom(ds);

	if (blps.status)
	{
		sock->abort();
		emit loginAborted(this, tr("failure at BNCS account logon proof (code %1)").arg(blps.status));
		return;
	}

	bngp.port = 6112;
	sendBncsMessage(ds, bngp);

	ec.user = "";
	ec.stat = "";
	sendBncsMessage(ds, ec);
}

void BncsClient::processBncsEnterChat(QDataStream& ds)
{
	BncsEnterChatServer ec;

	qDebug() << "Processing SID_ENTERCHAT_SRV.";

	ec.readFrom(ds);

	emit loginComplete(this);
}

void BncsClient::processBncsChatEvent(QDataStream& ds)
{
	BncsChatEvent ce;

	qDebug() << "Processing SID_CHATEVENT...";
	ce.readFrom(ds);

	switch (ce.eventId)
	{
		case EID_CHANNEL:
			this->channel = ce.text;
			emit chatJoinChannel(this, this->channel);
			break;

		case EID_SHOWUSER:
			emit chatJoin(this, ce.user, QString(ce.text).split(" ", QString::SkipEmptyParts), true);
			break;

		case EID_JOIN:
			emit chatJoin(this, ce.user, QString(ce.text).split(" ", QString::SkipEmptyParts), false);
			break;

		case EID_USERFLAGS:
			emit chatFlagChange(this, ce.user, QString(ce.text).split(" ", QString::SkipEmptyParts));
			break;

		case EID_LEAVE:
			emit chatLeave(this, ce.user);
			break;

		case EID_WHISPER:
			emit chatWhisper(this, ce.user, ce.text);
			break;

		case EID_WHISPERSENT:
			emit chatWhisperSent(this, ce.user, ce.text);
			break;

		case EID_TALK:
			emit chatTalk(this, ce.user, ce.text);
			break;

		case EID_EMOTE:
			emit chatEmote(this, ce.user, ce.text);
			break;

		case EID_BROADCAST:
			emit chatBroadcast(this, ce.user, ce.text);
			break;

		case EID_INFO:
			emit chatInfo(this, ce.text);
			break;

		case EID_ERROR:
			emit chatError(this, ce.text);
			break;

		default:
			break;
	}
}

void BncsClient::sockDisconnect()
{
	qDebug() << "Disconnected from Bncs (forced or delayed completion).";

	switch (state)
	{
		case SS_DISCONNECTED:
			qWarning() << "Received socket disconnect while client in disconnected state!";
			break;

		default:
			emit loginAborted(this, "the server disconnected");
			break;
	};

	state = SS_DISCONNECTED;
}

/*
		LoginResult result;
		switch (bacs.result)
		{
			case 0x100:
				result = LOGIN_FAILURE_VERSION_UPGRADE;
				break;

			case 0x101:
				result = LOGIN_FAILURE_VERSION_INVALID;
				break;

			case 0x102:
				result = LOGIN_FAILURE_VERSION_DOWNGRADE;
				break;

			case 0x200:
				result = LOGIN_FAILURE_KEY1_INVALID;
				break;

			case 0x201:
				result = LOGIN_FAILURE_KEY1_INUSE;
				break;

			case 0x202:
				result = LOGIN_FAILURE_KEY1_BANNED;
				break;

			case 0x203:
				result = LOGIN_FAILURE_KEY1_WRONGPRODUCT;
				break;

			case 0x210:
				result = LOGIN_FAILURE_KEY2_INVALID;
				break;

			case 0x211:
				result = LOGIN_FAILURE_KEY2_INUSE;
				break;

			case 0x212:
				result = LOGIN_FAILURE_KEY2_BANNED;
				break;

			case 0x213:
				result = LOGIN_FAILURE_KEY2_WRONGPRODUCT;
				break;

			default:
				result = LOGIN_FAILURE;
				break;
		}




				LoginResult result;
		switch (bals.status)
		{
			case 0x01:
				result = LOGIN_FAILURE_ACCOUNT_NOEXIST;
				break;

			case 0x05:
				result = LOGIN_FAILURE_ACCOUNT_UPGRADE;
				break;

			default:
				result = LOGIN_FAILURE;
				break;
		}




				LoginResult result;
		switch (blps.status)
		{
			case 0x02:
				result = LOGIN_FAILURE_ACCOUNT_BADPASSWORD;
				break;

			case 0x0E:
				result = LOGIN_FAILURE_ACCOUNT_NEEDSEMAIL;
				break;

			default:
				result = LOGIN_FAILURE;
				break;
		}
		*/

